

package com.github.florent37.runtimepermission;

import com.github.florent37.runtimepermission.callbacks.*;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.app.Context;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static ohos.bundle.IBundleManager.PERMISSION_DENIED;

/**
 * RuntimePermission
 *
 * @since 2021-03-13
 */
public class RuntimePermission {
    /**
     * To get class name
     */
    private static final String TAG = RuntimePermission.class.getName();

    /**
     * list of permission
     */
    private static PermissionListener permissionListener = null;

    public static final String LIST_PERMISSIONS = "LIST_PERMISSIONS";

    private final Reference<Ability> activityReference;

    /**
     * The list of permissions we want to ask
     */
    private final List<String> permissionsToRequest = new ArrayList<>();

    /**
     * region callbacks
     */
    private final List<ResponseCallback> responseCallbacks = new ArrayList<>();

    private final List<AcceptedCallback> acceptedCallbacks = new ArrayList<>();

    private final List<ForeverDeniedCallback> foreverDeniedCallbacks = new ArrayList<>();

    private final List<DeniedCallback> deniedCallbacks = new ArrayList<>();

    private final List<PermissionResultCallback> permissionListeners = new ArrayList<>();

    /**
     * the listener we will give to the fragment
     */
    private final PermissionListener listener = new PermissionListener() {
        @Override
        public void onRequestPermissionsResult(List<String> acceptedPermissions, List<String> refusedPermissions,
            List<String> askAgainPermissions) {
            HiLogs.e(TAG, "onRequestPermissionsResult() : acceptedPermissions : " + acceptedPermissions.size());
            onReceivedPermissionResult(acceptedPermissions, refusedPermissions, askAgainPermissions);
        }
    };
    /**
     * end region
     */
    /**
     * @param activity {@link Ability}
     */
    public RuntimePermission(@Nullable final Ability activity) {
        HiLogs.e(TAG, "RuntimePermission() : is Ability null : " + (activity == null));
        if (activity != null) {
            this.activityReference = new WeakReference<>(activity);
        } else {
            this.activityReference = new WeakReference<>(null);
        }
    }

    /**
     * Fill permissions to only ask If we do not call this method,
     * If not set or empty, the library will find all needed permissions to ask from manifest
     * You can call .request(permissions) after this method if you want to give permissions in a separate method
     *
     * @param activity {@link Ability}
     * @param permissions {@link String}
     * @return RuntimePermission
     */
    public static RuntimePermission askPermission(@Nullable final Ability activity, String... permissions) {
        HiLogs.e(TAG, "askPermission() : is Ability null : " + (activity == null));
        return new RuntimePermission(activity).request(permissions);
    }

    /**
     * PermissionListener
     *
     * @return permissionListener
     */
    public static PermissionListener getPermissionListener() {
        return permissionListener;
    }

    /**
     * Fill permissions to only ask If we do not call this method,
     * If not set or empty, the library will find all needed permissions to ask from manifest
     * You can call .request(permissions) after this method if you want to give permissions in a separate method
     *
     * @param fragment {@link AbilitySlice}
     * @param permissions {@link String}
     * @return askPermission
     */
    public static RuntimePermission askPermission(@Nullable final AbilitySlice fragment, String... permissions) {
        HiLogs.e(TAG, "askPermission() : is AbilitySlice null : " + (fragment == null));
        @Nullable Ability activity = null;
        if (fragment != null) {
            activity = fragment.getAbility();
        }
        return askPermission(activity).request(permissions);
    }

    /**
     * Just a helper methods in case the user blocks permission.
     * it goes to your application settings page for the user to enable permission again.
     */
    public void goToSettings() {
    }

    /**
     * onReceivedPermissionResult
     *
     * @param acceptedPermissions {@link List}
     * @param refusedPermissions {@link List}
     * @param askAgainPermissions {@link List}
     */
    private void onReceivedPermissionResult(List<String> acceptedPermissions, List<String> refusedPermissions,
        List<String> askAgainPermissions) {
        HiLogs.e(TAG, "onReceivedPermissionResult() : acceptedPermissions : " + (acceptedPermissions.size()));
        final PermissionResult permissionResult = new PermissionResult(this, acceptedPermissions, refusedPermissions,
            askAgainPermissions);
        if (permissionResult.isAccepted()) {
            for (AcceptedCallback callback : acceptedCallbacks) {
                callback.onAccepted(permissionResult);
            }
            for (PermissionResultCallback callback : permissionListeners) {
                callback.onAccepted(permissionResult, permissionResult.getAccepted());
            }
        }
        if (permissionResult.hasDenied()) {
            for (DeniedCallback callback : deniedCallbacks) {
                callback.onDenied(permissionResult);
            }
        }
        if (permissionResult.hasForeverDenied()) {
            for (ForeverDeniedCallback callback : foreverDeniedCallbacks) {
                callback.onForeverDenied(permissionResult);
            }
        }
        if (permissionResult.hasForeverDenied() || permissionResult.hasDenied()) {
            for (PermissionResultCallback permissionListener : permissionListeners) {
                permissionListener.onDenied(permissionResult, permissionResult.getDenied(),
                    permissionResult.getForeverDenied());
            }
        }

        for (ResponseCallback responseCallback : responseCallbacks) {
            responseCallback.onResponse(permissionResult);
        }
    }

    /**
     * We want to only request given permissions
     * If we do not call this method, the library will find all needed permissions to ask from manifest
     *
     * @param permissions {@link List}
     * @return this
     */
    public RuntimePermission request(@Nullable final List<String> permissions) {
        HiLogs.e(TAG, "request() : permissions : " + (permissions.size()));
        if (permissions != null) {
            permissionsToRequest.clear();
            permissionsToRequest.addAll(permissions);
        }
        return this;
    }

    /**
     * We want to only request given permissions
     *
     * @param permissions
     * @return this
     */
    public RuntimePermission request(@Nullable final String... permissions) {
        HiLogs.e(TAG, "request() : permissions : " + permissions.length);
        if (permissions != null) {
            return this.request(Arrays.asList(permissions));
        } else {
            return this;
        }
    }

    /**
     * onResponse
     *
     * @param callback {@link ResponseCallback}
     * @return this
     */
    public RuntimePermission onResponse(@Nullable final ResponseCallback callback) {
        HiLogs.e(TAG, "onResponse() : ResponseCallback : " + callback);
        if (callback != null) {
            responseCallbacks.add(callback);
        }
        return this;
    }

    /**
     * onResponse
     *
     * @param permissionListener {@link PermissionResultCallback}
     * @return this
     */
    public RuntimePermission onResponse(@Nullable final PermissionResultCallback permissionListener) {
        HiLogs.e(TAG, "onResponse() : PermissionListener : " + permissionListener);
        if (permissionListener != null) {
            permissionListeners.add(permissionListener);
        }
        return this;
    }

    /**
     * onAccepted
     *
     * @param callback {@link AcceptedCallback}
     * @return this
     */
    public RuntimePermission onAccepted(@Nullable final AcceptedCallback callback) {
        HiLogs.e(TAG, "onAccepted() : AcceptedCallback : " + callback);
        if (callback != null) {
            acceptedCallbacks.add(callback);
        }
        return this;
    }

    /**
     * onDenied
     *
     * @param callback {@link DeniedCallback}
     * @return this
     */
    public RuntimePermission onDenied(@Nullable final DeniedCallback callback) {
        HiLogs.e(TAG, "onDenied() : DeniedCallback : " + callback);
        if (callback != null) {
            deniedCallbacks.add(callback);
        }
        return this;
    }

    /**
     * onForeverDenied
     *
     * @param callback {@link ForeverDeniedCallback}
     * @return this
     */
    public RuntimePermission onForeverDenied(@Nullable final ForeverDeniedCallback callback) {
        if (callback != null) {
            foreverDeniedCallbacks.add(callback);
        }
        return this;
    }

    /**
     * ask
     *
     * @param responseCallback {@link ResponseCallback}
     */
    public void ask(@Nullable ResponseCallback responseCallback) {
        onResponse(responseCallback).ask();
    }

    /**
     * ask
     *
     * @param permissionListener {@link PermissionResultCallback}
     */
    public void ask(@Nullable PermissionResultCallback permissionListener) {
        HiLogs.e(TAG, "ask() : PermissionListener : " + permissionListener);
        onResponse(permissionListener).ask();
    }

    /**
     * If we request permission using .request(names), we only ask them
     * If not, this lib will search needed permissions from Manifest
     *
     * @param context {@link Context}
     * @return permissionsToRequest
     */
    private List<String> findNeededPermissions(@NotNull Context context) {
        return permissionsToRequest;
    }

    /**
     * Ask for the permission. Which permission? Anything you register on your manifest that needs it.
     * It is safe to call this every time without querying `shouldAsk`.
     * In case you call `ask` without any permission, the method returns.
     */
    public void ask() {
        final Ability activity = activityReference.get();
        HiLogs.e(TAG, "ask() : activity is null : " + (activity == null));
        if (activity == null || activity.isTerminating()) {
            return;
        }
        /**
         * retrieve permissions we want
         */
        final List<String> permissions = findNeededPermissions(activity);

        if (permissions.isEmpty() || arePermissionsAlreadyAccepted(activity, permissions)) {
            onAllAccepted(permissions);
        } else if (activity != null) {
            permissionListener = listener;
            Operation operation = new Intent.OperationBuilder().withBundleName("com.github.florent37.runtimepermission")
                .withAbilityName(PermissionAbility.class.getSimpleName())
                .build();
            Intent intent = new Intent();
            intent.setOperation(operation);
            intent.setStringArrayListParam(LIST_PERMISSIONS, (ArrayList<String>) permissions);
            activity.startAbility(intent);
        }
    }

    /**
     * check are permissions already accepted or not
     *
     * @param context {@link Ability}
     * @param permissions {@link List}
     * @return true
     */
    private boolean arePermissionsAlreadyAccepted(@NotNull Ability context, @NotNull final List<String> permissions) {
        for (String permission : permissions) {
            final int permissionState = context.verifySelfPermission(permission);
            if (permissionState == PERMISSION_DENIED) {
                return false;
            }
        }
        return true;
    }

    /**
     * onAllAccepted
     *
     * @param permissions {@link List}
     */
    private void onAllAccepted(@NotNull final List<String> permissions) {
        onReceivedPermissionResult(permissions, null, null);
    }

    /**
     * used to ask permission again
     */
    public void askAgain() {
    }
}
